package com.bood.shimmer.shiro;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.bood.shimmer.common.service.RedisService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;

/**
 * <p>
 * JWT 工具
 * </p>
 *
 * @author：bood
 * @date：2020/9/25
 */
@Component
public class JwtUtil {

    /**
     * JWT 令牌存储标识
     */
    @Value("${token.key}")
    private String key;

    /**
     * JWT 令牌秘钥
     */
    @Value("${token.secret}")
    private String secret;

    /**
     * JWT 令牌有效期
     */
    @Value("#{new Integer('${token.expireTime}')}")
    private Integer expireTime;

    @Resource
    private RedisService redisService;


    /**
     * <p>
     * 创建 token
     * 注：这里的 token 会被缓存到 Redis 中,用作为二次验证
     * Redis 里面缓存的时间应该和 jwt token 的过期时间设置相同
     * </p>
     *
     * @param jwtToken: Token 定制的信息
     * @return：java.lang.String
     * @author：bood
     * @date：2020/9/25
     */
    public String createToken(JwtToken jwtToken) throws Exception {
        // JWT ID
        String jwtId = jwtToken.getId();
        // 1.加密算法进行签名得到 token
        Algorithm algorithm = Algorithm.HMAC256(secret);
        String token = JWT.create()
                .withClaim("id", jwtId)
                .withClaim("ip", jwtToken.getIp())
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime * 1000))
                .sign(algorithm);
        // 2.Redis 缓存 JWT, 注:请和 JWT 过期时间一致
        redisService.set(key + jwtId, token, expireTime);
        return token;
    }

    /**
     * <p>
     * 校验 token 是否正确
     * 1.根据 token 解密，解密出 jwt-id , 先从 Redis 中查找出 redisToken，匹配是否相同
     * 2.然后再对 redisToken 进行解密，解密成功则继续流程 和 进行 token 续期
     * </p>
     *
     * @param token: token
     * @return：boolean
     * @author：bood
     * @date：2020/9/25
     */
    public boolean verifyToken(String token) {
        try {
            // 1.根据 token 解密，解密出 jwt-id, 先从 Redis 中查找出 redisToken，匹配是否相同
            String redisToken = (String) redisService.get(key + getIdByToken(token));
            if (!redisToken.equals(token)) {
                return false;
            }
            // 2.得到算法相同的 JWTVerifier
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("id", getIdByToken(redisToken))
                    .withClaim("ip", getIpByToken(redisToken))
                    .acceptExpiresAt(System.currentTimeMillis() + expireTime * 1000)
                    .build();
            // 3.验证 token
            verifier.verify(redisToken);
            // 4.Redis 缓存 JWT 续期
            redisService.expire(key + getIdByToken(token), expireTime);
            return true;
        } catch (Exception e) {
            // 捕捉到任何异常都视为校验失败
            return false;
        }
    }

    /**
     * <p>
     * 根据 Token 获取 ID
     * </p>
     *
     * @param token: token
     * @return：java.lang.Integer
     * @author：bood
     * @date：2020/9/25
     */
    public String getIdByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim("id").asString();
    }

    /**
     * <p>
     * 根据 Token 获取 IP
     * </p>
     *
     * @param token: token
     * @return：java.lang.Integer
     * @author：bood
     * @date：2020/9/25
     */
    public Integer getIpByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim("ip").asInt();
    }

}
